import AppConstants from "../AppConstants"
import AppUtil from "../AppUtil"
import EventMgr from "../manager/EventMgr"
import ResMgr from "../manager/ResMgr"
import LayerMgr from "./LayerMgr"
import PanelBase from "./PanelBase"
import TopBlock from "./TopBlock"


/**
 * @description 面板管理器
 * @author mirahs
 * @email 2892727493@qq.com
 */
export default class PanelMgr {
    // 所有面板
    private static _panels: Map<string, PanelBase> = new Map()

    private static _maskName = '_mask'
    private static _maskPrefab: cc.Node


    public static init() {
        this._panels.clear()

        if (this._maskPrefab && this._maskPrefab.isValid) return

        this._maskPrefab = AppUtil.newSpriteNode(this._maskName)
        this._maskPrefab.color = cc.Color.BLACK
        this._maskPrefab.opacity = 0
    }

    // 显示 Panel
    public static async show(panelCls: any, ...panelArgs: any[]) {
        TopBlock.show()

        const cls = new panelCls() as PanelBase
        const viewName = cls.getClassName()

        let current = this._panels.get(viewName)
        if (!current) {
            current = cls
            current.panelName = viewName

            let skinNode: cc.Node
            
            if (current.bundleName == 'null') {
                const panelPrefab = cc.find(current.skinPath, cc.Canvas.instance.node)
                if (!panelPrefab) {
                    TopBlock.hide()
                    console.error(`PanelMgr.show cc.find 找不到[${viewName}]面板场景对象:${current.skinPath}`)
                    return
                }
                skinNode = cc.instantiate(panelPrefab)
            } else {
                const [panelPrefab, err] = await AppUtil.asyncWrap<cc.Prefab>(ResMgr.load(current.bundleName, current.skinPath))
                if (err) {
                    TopBlock.hide()
                    console.error(`PanelMgr.show load bundleName:${current.bundleName},skinPath:${current.skinPath},err:${err}`)
                    return
                }
                skinNode = cc.instantiate(panelPrefab)
            }

            current.init(panelArgs)
            current.skin = skinNode
            current.initDone()

            LayerMgr.setLayer(current.skin, AppConstants.viewLayer.Panel)
            this.maskStyle(current)

            this._panels.set(viewName, current)
        } else {
            current.reset(panelArgs)

            LayerMgr.setLayer(current.skin, AppConstants.viewLayer.Panel)
        }

        current.skin.active = false
        current.showing()

        this.startShowPanel(current, current.panelShowStyle, true)
    }

    // 判断 Panel 是否打开
    public static isAlive(panelCls: any): boolean {
        const viewName = AppUtil.getClassName(panelCls)

        return this._panels.has(viewName) && this._panels.get(viewName).skin.active
    }

    // 更新 Panel
    public static updateView(panelCls: any, ...args: any[]) {
        const viewName = AppUtil.getClassName(panelCls)

        if (!this.isAlive(panelCls)) {
            console.error('PanelMgr.updateView [%s] 更新 view 失败, 未打开或未激活', viewName)
            return
        }

        this._panels.get(viewName).updateView(args)
    }

    // 隐藏 Panel
    public static hide(panel: string | any) {
        const panelName = typeof panel === 'string' ? panel : AppUtil.getClassName(panel)

        if (!this._panels.has(panelName)) {
            console.error(`PanelMgr.hide 面板[${panelName}]不存在`)
            return
        }

        const pb = this._panels.get(panelName)
        if (!pb.skin.active) {
            console.error(`PanelMgr.hide 面板[${panelName}]已隐藏`)
            return
        }

        TopBlock.show()

        this.startShowPanel(pb, pb.panelShowStyle, false)
    }

    // 隐藏所有 Panel
    public static hideAll() {
        this._panels.forEach(panel => {
            if (panel.skin.active) {
                this.destroy(panel.panelName)
            }
        })

        EventMgr.emit(AppConstants.event.PanelHide)
    }

    // 销毁 Panel
    public static destroy(panelName: string, force: boolean = false) {
        if (!this._panels.has(panelName)) {
            console.error(`PanelMgr.destroy 面板[${panelName}]不存在`)
            return
        }

        let pb = this._panels.get(panelName)
        pb.hiding()
        pb.skin.active = false
        pb.hided()

        if (force || !pb.cache) {
            this._panels.delete(panelName)

            pb.destroy()
            pb = null
        }
    }


    private static maskStyle(go: PanelBase): void {
        const mask = cc.instantiate(this._maskPrefab)
        go.skin.addChild(mask, -1) //渲染在 skin 最底层
        AppUtil.setWidget(mask, go.skin.parent) //跟 Panel 层级一样大小

        if ((go.panelMaskStyle & AppConstants.panelMaskStyle.NoThrough) > 0) {
            mask.addComponent(cc.BlockInputEvents)
        }
        if ((go.panelMaskStyle & AppConstants.panelMaskStyle.Close) > 0) {
            // 面板如果没设置 NoThrough 属性是不会有 cc.BlockInputEvents 组件的
            if (!mask.getComponent(cc.BlockInputEvents)) {
                mask.addComponent(cc.BlockInputEvents)
            }

            // 点击面板内的ui元素不会关闭面板
            mask.on(cc.Node.EventType.TOUCH_START, (et: cc.Event.EventTouch) => {
                const worldPos = et.getLocation()
                for (const child of go.skin.children) {
                    if (child == mask) continue
                    if (child.getBoundingBoxToWorld().contains(worldPos)) {
                        return
                    }
                }

                this.hide(go.panelName)
            })
        }
    }

    private static startShowPanel(go: PanelBase, showStyle: number, isOpen: boolean) {
        switch (showStyle) {
            case AppConstants.panelShowStyle.Normal:
                this.showNormal(go, isOpen)
                break
            case AppConstants.panelShowStyle.CenterSmallToBig:
                this.showCenterScale(go, isOpen)
                break
            case AppConstants.panelShowStyle.LeftToCenter:
                this.showSideToCenter(go, cc.v3(-cc.winSize.width, 0, 0), isOpen)
                break
            case AppConstants.panelShowStyle.RightToCenter:
                this.showSideToCenter(go, cc.v3(cc.winSize.width, 0, 0), isOpen)
                break
            case AppConstants.panelShowStyle.UpToCenter:
                this.showSideToCenter(go, cc.v3(0, cc.winSize.height, 0), isOpen)
                break
            case AppConstants.panelShowStyle.DownToCenter:
                this.showSideToCenter(go, cc.v3(0, -cc.winSize.height, 0), isOpen)
                break
            case AppConstants.panelShowStyle.Custom:
                if (isOpen && !go.tweenShow) {
                    this.showNormal(go, isOpen)
                } else if (!isOpen && !go.tweenHide) {
                    this.showNormal(go, isOpen)
                } else {
                    this.showCustom(go, isOpen)
                }
                break
            default:
                console.error(`PanelMgr.startShowPanel showStyle 错误:${showStyle}`)
                break
        }
    }

    //#region 显示方式
    /** 默认显示 */
    private static showNormal(go: PanelBase, isOpen: boolean) {
        this.showPanelBefore(go, isOpen)

        if (isOpen) {
            go.skin.active = true
            go.showed()
        } else {
            this.destroy(go.panelName)
        }

        this.showPanelAfter(go, isOpen)
    }

    /** 中间变大 */
    private static showCenterScale(go: PanelBase, isOpen: boolean) {
        this.showPanelBefore(go, isOpen)

        if (isOpen) {
            go.skin.scale = 0
            go.skin.active = true
            cc.tween(go.skin).to(go.duration, { scale: 1 }, go.easingShow).call(() => {
                go.showed()
                this.showPanelAfter(go, isOpen)
            }).start()
        } else {
            cc.tween(go.skin).to(go.duration, { scale: 0 }, go.easingHide).call(() => {
                this.destroy(go.panelName)
                this.showPanelAfter(go, isOpen)
            }).start()
        }
    }

    /** 从四边移动到中间 */
    private static showSideToCenter(go: PanelBase, from: cc.Vec3, isOpen: boolean) {
        this.showPanelBefore(go, isOpen)
        const to = cc.Vec3.ZERO

        if (isOpen) {
            go.skin.setPosition(from)
            go.skin.active = true
            cc.tween(go.skin).to(go.duration, { position: to }, go.easingShow).call(() => {
                go.showed()
                this.showPanelAfter(go, isOpen)
            }).start()
        } else {
            cc.tween(go.skin).to(go.duration, { position: from }, go.easingHide).call(() => {
                this.destroy(go.panelName)
                this.showPanelAfter(go, isOpen)
            }).start()
        }
    }

    /** 自定义 */
    private static showCustom(go: PanelBase, isOpen: boolean) {
        this.showPanelBefore(go, isOpen)

        if (isOpen) {
            go.skin.active = true
            go.tweenShow.call(() => {
                go.showed()
                this.showPanelAfter(go, isOpen)
            }).start()
        } else {
            go.tweenHide.call(() => {
                this.destroy(go.panelName)
                this.showPanelAfter(go, isOpen)
            }).start()
        }
    }
    //#endregion


    private static showPanelBefore(go: PanelBase, isOpen: boolean) {
        // 如果有 半透明 组件，先隐藏组件
        if (!isOpen && (go.panelMaskStyle & AppConstants.panelMaskStyle.Black) > 0) {
            const mask = go.skin.getChildByName(this._maskName)
            mask.opacity = 0
        }
    }

    private static showPanelAfter(go: PanelBase, isOpen: boolean) {
        // 重置位置(有移动位置的显示方式遮罩位置会错乱)
        const mask = go.skin.getChildByName(this._maskName)
        mask.setPosition(cc.Vec2.ZERO)

        if (isOpen && (go.panelMaskStyle & AppConstants.panelMaskStyle.Black) > 0) {
            // 有快速调用 cc.director.pause() 的面板需要把时间设为 0
            if (go.blackMaskDuration <= 0) {
                mask.opacity = go.blackMaskOpacity
                this.showPanelAfter2(isOpen)
            } else {
                cc.tween(mask).to(go.blackMaskDuration, { opacity: go.blackMaskOpacity }).call(() => this.showPanelAfter2(isOpen)).start()
            }
        } else {
            this.showPanelAfter2(isOpen)
        }
    }

    private static showPanelAfter2(isOpen: boolean) {
        TopBlock.hide()

        if (isOpen) {
            EventMgr.emit(AppConstants.event.PanelShow)
        } else {
            let hide = true
            this._panels.forEach(panel => {
                if (panel.skin.active) hide = false
            })
            hide && EventMgr.emit(AppConstants.event.PanelHide)
        }
    }
}
